<?php
/**
 * Inter组件之Session操作器：session模拟操作器。
 * 不允许自行使用$_SESSION数组完成对session的操作，必须透过此类提供的方法完成对模拟session存取操作
 * 纯php session id生成和校验参考ecmall{@link http://ecmall.shopex.cn/}思路，在此感谢
 * 
 * @author Horse Luke<horseluke@126.com>
 * @copyright Horse Luke, 2010
 * @license the Apache License, Version 2.0 (the "License"). {@link http://www.apache.org/licenses/LICENSE-2.0}
 * @version $Id: Simulator.php 160 2011-02-06 08:02:14Z horseluke@126.com $
 * @package Inter_Session
 * @subpackage Inter_Session_Operator
 *
 */
class Inter_Session_Operator_Simulator extends Inter_Session_Operator_Abstract{
    
    /**
     * session机制是否已经启动？
     * @var bool
     */
    protected $_started = false;
    
    /**
     * session数据
     * @var array
     */
    protected $_sess_data = array();
    
    /**
     * session id
     * @var string
     */
    protected $_session_id = '';
    
    /**
     * session存储器实例
     * @var Inter_Session_Storage_Abstract
     */
    protected $_storageHandler = null;
    
    /**
     * 配置选项数组
     * @var array
     */
    protected $_config = array();
    
    /**
     * 构造方法，并导入配置
     * @return Inter_Session_Operator_Simulator
     */
    public public function __construct(){
        $this->_readConfig();
    }
    
    /**
     * 注册一个session存储器实例。
     * 本类模仿session_set_save_handler
     * 对于本模拟器来讲，这个是必须要执行的方法，并且要在本类的session_start之前启动。
     * @param Inter_Session_Storage_Abstract &$handler
     * @return bool
     */
    public function setStorageHandler(&$handler){
        if(true == $this->_started){
            return false;
        }
        if(!$handler instanceof Inter_Session_Storage_Abstract) {
        	throw new Exception('The handler is not instance of Inter_Session_Storage_Abstract');
        }
        $this->_storageHandler = $handler;
        return true;
    }
    
    /**
     * 清除session
     */
    public function clear(){
        $this->_sess_data = array();
    }
    
    /**
     * 设置session
     * @param mixed $k 如果传入数组，则表示批量设置
     * @param mixed $v
     */
    public function set($k,$v = false){
        if( is_array($k) ){
            $this->_sess_data = array_merge($this->_sess_data,$k);
        }else{
            $this->_sess_data[$k] = $v;
        }
    }
    
    /**
     * 获取session
     * @param mixed $key 如果留空，则表示返回整个session
     * @return mixed
     */
    public function get($key = null){
        if( null !==  $key ){
            return isset($this->_sess_data[$key]) ? $this->_sess_data[$key] : null;
        }else{
            return $this->_sess_data;
        }
    }
    
    /**
     * 删除某个/某组session
     * @param mixed $k 如果传入数组，则表示批量删除
     * @return bool
     */
    public function del($k){
        if ( empty($this->_sess_data) ){
            return true;
        }
        if(!is_array($k)){
            $k = array($k);
        }
        foreach($k as $kv ){
            if (isset($this->_sess_data[$kv])){
                unset($this->_sess_data[$kv]);
            }
        }
        return true;
    }
    
    /**
     * 模拟php函数session_start，启动session机制。
     * @return bool
     */
    public function session_start(){
        if( true == $this->_started ){
            trigger_error("A session had already been started - ignoring ". __CLASS__. "->session_start()", E_USER_NOTICE);
            return false;
        }
        
        if(empty($this->_session_id)){
            $this->_session_id = $this->_checkSessionHash($this->_getSessionIdFromOuter());
            //session id校验失败时，重新生成并获取新的session id
            if( empty($this->_session_id) ){
                $this->session_regenerate_id();
            }
        
        }else{
            $this->_sendSessionCookie($this->_session_id);
        }
        
        $this->_storageHandler->open(null, null);
        $this->session_decode( $this->_storageHandler->read( $this->_session_id ) );
        
        $this->_started = true;
        return true;
    }
    
    /**
     * 模拟php函数session_id
     * @param string $id 不为空则表示设置session_id为传入的值
     * @return string
     */
    public function session_id( $id = null ){
        if( !empty($id) ){
            $this->_session_id = (string)$id;
        }
        return $this->_session_id;
    }
    
    /**
     * 模拟php函数session_name
     * 请注意本模拟不检查合法性
     * @param string $name 不为空则表示设置session.name
     * @return string
     */
    public function session_name($name = null){
        if( !empty($name) ){
            $this->_config['session.name'] = (string)$name;
        }
        return $this->_config['session.name'];
    }
    
    /**
     * 模拟php函数session_regenerate_id，重新生成并设置一个session_id
     * 但此处和session_regenerate_id有些不同的是，它不会自动close Storage
     * @param bool $delete_old_session 是否删除以前的session
     * @return bool
     */
    public function session_regenerate_id( $delete_old_session = false ){
        if( $delete_old_session ){
            if( is_object($this->_storageHandler) && !empty($this->_session_id) ){
               $this->_storageHandler->destroy($this->_session_id);
            }
            $this->_sess_data = array();
        }
        
        $this->_session_id = $this->_generateSessionid();
        $this->_sendSessionCookie($this->_session_id);
        
        return true;
    }
    
    /**
     * 模拟php函数session_destroy
     * 删除当前用户对应的session文件以及释放session id，内存中的$_SESSION变量内容依然保留
     * @return bool
     */
    public function session_destroy(){
        if( is_object($this->_storageHandler) && !empty($this->_session_id) ){
            $this->_storageHandler->destroy($this->_session_id);
        }
        $this->_session_id = '';
        $this->_started = false;
        return true;
    }
    
    /**
     * 模拟php函数session_unset
     * 释放当前在内存中已经创建的所有$_SESSION变量，但不删除session文件以及不释放对应的session id
     */
    public function session_unset(){
        $this->clear();
    }
    
    /**
     * 模拟php函数session_encode
     * 将当前会话数据编码为一个字符串并返回；失败则发出WARNING并返回false
     * 本模拟方法使用serialize
     * @return string|bool
     */
    public function session_encode(){
        if(!is_array($this->_sess_data)){
            trigger_error(__CLASS__. '->'. __METHOD__. '(): Cannot encode non-existent session.', E_USER_WARNING);
            return false;
        }
        return serialize($this->_sess_data);
    }
    
    /**
     * 模拟php函数session_decode
     * Decodes session data from a string
     * 本模拟方法使用unserialize
     * @param string $data
     * @return bool
     */
    public function session_decode($data){
        if(empty($data)){
            return false;
        }
        $sess_data = unserialize($data);
        if( is_array($sess_data) ){
            $this->set($sess_data);
            return true;
        }else{
            return false;
        }
    }
    
    /**
     * 模拟php函数session_cache_limiter
     * Get and/or set the current cache limiter
     * @param string $cache_limiter 可选值：none/nocache/private/private_no_expire/public
     * @return string
     */
    public function session_cache_limiter($cache_limiter = null){
        if(null !== $cache_limiter){
            $this->_config['session.cache_limiter'] = (string)$cache_limiter;
        }
        return $this->_config['session.cache_limiter'];
    }
    
    /**
     * 模拟php函数session_cache_expire
     * Return current cache expire
     * @param integer $new_cache_expire
     * @return integer
     */
    public function session_cache_expire($new_cache_expire = null){
        if( null !== $new_cache_expire ){
            $this->_config['session.cache_expire'] = (int)$new_cache_expire;
        }
        return $this->_config['session.cache_expire'];
    }
    
    /**
     * 模拟php函数session_get_cookie_params
     * Get the session cookie parameters
     * @return array
     */
    public function session_get_cookie_params(){
        return array(
            "lifetime" => $this->_config['session.cookie_lifetime'],
            "path" => $this->_config['session.cookie_path'],
            "domain" => $this->_config['session.cookie_domain'],
            "secure" => $this->_config['session.cookie_secure'],
            "httponly" => $this->_config['session.cookie_httponly'],        
        );
    }
    
    /**
     * 模拟php函数session_set_cookie_params
     * Set the session cookie parameters
     * @param int $lifetime
     * @param string $path
     * @param string $domain
     * @param bool $secure
     * @param bool $httponly
     */
    public function session_set_cookie_params($lifetime, $path = null, $domain = null, $secure = null, $httponly = null){
        $this->_config['session.cookie_lifetime'] = (int)$lifetime;
        (null !== $path) && $this->_config['session.cookie_path'] = (string)$path;
        (null !== $domain) && $this->_config['session.cookie_domain'] = (string)$domain;
        (null !== $secure) && $this->_config['session.cookie_secure'] = (bool)$secure;
        (null !== $httponly) && $this->_config['session.cookie_httponly'] = (bool)$httponly;
    }
    
    /**
     * 模拟php函数session_save_path
     * @param string $path
     * @return string
     */
    public function session_save_path($path = null){
        if(null !== $path){
            $this->_config['session.save_path'] = (string)$path;
        }
        return $this->_config['session.save_path'];
    }
    
    /**
     * 模拟php函数session_write_close
     * 但此处和session_write_close不同的是，不会自动close Storage
     */
    public function session_write_close(){
        $this->_saveSessionToStorage(false);
        $this->clear();
        $this->_session_id = '';
        $this->_started = false;
    }
    
    /**
     * 魔术方法调用，直接抛出没有对应模拟方法异常
     * @param string $method
     * @param array $param
     */
    public function __call($method, $param){
        throw new Exception(__CLASS__. ' does not have this session simulate function:'. $method);
    }
    
    /**
     * 析构函数
     */
    public function __destruct(){
        $this->_saveSessionToStorage(true);
    }
    
    /**
     * 获取本类的设置
     * @param mixed $name 为空则表示返回整个$_config属性
     * @return mixed
     */
    public function getConfig($name=null){
        if(empty($name)){
            return $this->_config;
        }else{
            return isset($this->_config[$name]) ? $this->_config[$name] : null;
        }
    }
    
    /**
     * 执行session存储器实例，将模拟的session内容写入保存
     * @param bool $closeStorage 写入后，是否执行session存储器实例的close方法？默认为是
     * @return integer 执行结果
     */
    protected function _saveSessionToStorage($closeStorage = true){
        if( !is_object($this->_storageHandler) ){
            return -1;
        }
        
        $return = 0;
        if( true == $this->_started && !empty($this->_session_id) ){
            $sess_data = $this->session_encode();
            if(is_string($sess_data)){
                $this->_storageHandler->write($this->_session_id, $sess_data);
            }else{
                $return = -3;
            }
        }else{
            $return = -2;
        }
        
        if(true == $closeStorage){
            $this->_storageHandler->close();
        }
        return $return;
        
    }
    
    /**
     * 模拟php，生成一个session_id，长度为32位
     * @return string
     */
    protected function _generateSessionid(){
        $prefix = mt_rand(). $this->getIP();
        
        $sessionid = md5(uniqid( $prefix, true ));
        return $sessionid;
    }
    
    /**
     * 生成一个session id校验
     * @param string $id
     * @return string
     */
    protected function _generateSessionHash( $id ){
        $key = '';
        if( !empty($_SERVER['HTTP_USER_AGENT']) ){
            $key .= $_SERVER['HTTP_USER_AGENT'];
        }
        $key = $this->_config['SESSION_HASHKEY']. $this->getIP(). $id;
        return sprintf('%08x', crc32($key));
    }
    
    /**
     * session校验，并返回真实的session id值
     * 
     * @param string $hash
     * @return string
     */
    protected function _checkSessionHash($hash){
        if( empty($hash) ){
            return '';
        }
        $tmp_session_id = substr ( $hash, 0, 32 );
        
        if ($this->_generateSessionHash ( $tmp_session_id ) == substr ( $hash, 32 )) {
            return $tmp_session_id;
        } else {
            return '';
        }
    }
    
    /**
     * 读取本类的配置文件，并且存放到$this->_config中
     */
    protected function _readConfig(){
        $this->_config = require dirname(__FILE__). '/../../../../Config/'. __CLASS__. '.php';
    }
    
    /**
     * 根据session.cache_limiter，发送对应的header。成功返回true，否则为false
     * @return bool
     */
    protected function _sendSessionCacheHeader(){
        $headername = 'header_cache_limiter_'. (string)$this->_config['session.cache_limiter'];
        if(!isset($this->_config[$headername])){
            return false;
        }
        
        $replaceTable = array(
                                '%timestamp%' => time(),
                                '%expire%' => $this->_config['session.cache_expire'] * 60,
                             );
        //格式化时间
        $replaceTable['%nexttime%'] = gmdate("D, d M Y H:i:s", $replaceTable['%timestamp%'] + $replaceTable['%expire%']) . " GMT";
        $replaceTable['%timestamp%'] = gmdate("D, d M Y H:i:s", $replaceTable['%timestamp%']) . " GMT";
        
        $headers = explode("\n\r", strtr($this->_config[$headername], $replaceTable));
        foreach($headers as $k => $headerline){
            header( $headerline );
        }
        
        return true;
    }
    
    /**
     * 发送session cookie
     * @param string $session_id
     * @return bool
     */
    protected function _sendSessionCookie($session_id){
        if(!$this->_config['session.use_cookies']){
            return false;
        }
        
        $this->_sendSessionCacheHeader();
        
        $session_id_cookie = $session_id. $this->_generateSessionHash($session_id);
        $path_cookie = ($this->_config['session.cookie_httponly'] && PHP_VERSION < '5.2.0') ? 
                    $this->_config['session.cookie_path']. "; HttpOnly" : 
                    $this->_config['session.cookie_path']
        ;
        $lifetime_cookie = (0 == $this->_config['session.cookie_lifetime']) ? 0 : time() + $this->_config['session.cookie_lifetime'];
        
        setcookie($this->_config['session.name'], 
                  $session_id_cookie, 
                  $lifetime_cookie,
                  $path_cookie,
                  $this->_config['session.cookie_domain'],
                  $this->_config['session.cookie_secure'],
                  $this->_config['session.cookie_httponly']
        );
        return true;
    }
    
    /**
     * 获取外部输入的session_id
     * @return string
     */
    protected function _getSessionIdFromOuter(){
        $session_id = '';
        
        if(true == $this->_config['session.use_cookies'] || true == $this->_config['session.use_only_cookies']){
            $session_id = isset($_COOKIE[$this->_config['session.name']]) ? (string)$_COOKIE[$this->_config['session.name']] : '';
        }
        
        if(empty($session_id)
               && false == $this->_config['session.use_only_cookies'] 
               && true == $this->_config['session.use_trans_sid']
               && isset($_GET[$this->_config['session.name']])
        ){
            $session_id = (string)$_GET[$this->_config['session.name']];
        }
        
        return $session_id;
    }
    
    /**
     * 获取ip
     * @return string
     */
    public function getIP(){
        if (getenv ( "HTTP_CLIENT_IP" ) && strcasecmp ( getenv ( "HTTP_CLIENT_IP" ), "unknown" )) {
            $ip = getenv ( "HTTP_CLIENT_IP" );
        } else if (getenv ( "HTTP_X_FORWARDED_FOR" ) && strcasecmp ( getenv ( "HTTP_X_FORWARDED_FOR" ), "unknown" )) {
            $ip = getenv ( "HTTP_X_FORWARDED_FOR" );
        } else if (getenv ( "REMOTE_ADDR" ) && strcasecmp ( getenv ( "REMOTE_ADDR" ), "unknown" )) {
            $ip = getenv ( "REMOTE_ADDR" );
        } else if (isset ( $_SERVER ['REMOTE_ADDR'] ) && $_SERVER ['REMOTE_ADDR'] && strcasecmp ( $_SERVER ['REMOTE_ADDR'], "unknown" )) {
            $ip = $_SERVER ['REMOTE_ADDR'];
        } else {
            $ip = "unknown";
        }
        
        return ( $ip == 'unknown' || ip2long ( $ip ) === false || ip2long ( $ip ) == -1 ) ? '0.0.0.0' : $ip;
    }
    
}